/*
 * UVic SENG 265, Fall 2011, A#4
 * Author: Lee D Gauthier
 * ID: V00681729
 *  
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "uvic_formatter.h"

/*Function definitions*/
int check_formatting(char* line);
char **format_list(char **words);
void *smalloc(size_t size);
void *srealloc(void * ptr , size_t size);
void marg();
void add_to_buffer();
void make_line(char *line);
void param_scan(char *cp, int *num);
    
/*Struct Definitions*/
typedef struct SETTINGS {
    int pagewidth;
    int margin;
    int mode;
} SETTINGS;

/*Globals*/
SETTINGS settings = { .pagewidth = 0, .margin = 0, .mode = 0};
char **format_buffer = NULL;
char *buffer = NULL;
int buffer_lines = 0;

/*Functions*/

/* A Small Wrapper for malloc to deal with NULL pointers
 * Inputs:
 *      size: Size of memory to me allocated
 * Returns:
 *      tmp: Pointer to some memory
 */
void *smalloc(size_t size){
    void *tmp = malloc(size);
    if(tmp == NULL){
        fprintf(stderr, "malloc of %zu bytes failed\n", size);
        exit(1);
    }
    return tmp;
}

/* A Small Wrapper for realloc to deal with NULL pointers
 * Inputs:
 *      size: Size of memory to me allocated
 *      ptr:  NULL pointer or a pointer to some Malloc'd memory
 * Returns:
 *      tmp: Pointer to some memory
 */
void *srealloc(void * ptr , size_t size){
    /*printf("size:-%zu-\n",size);*/
    void *tmp = realloc(ptr , size);
    if(tmp == NULL){
        fprintf(stderr,"realloc of %zu bytes failed\n", size);
        exit(1);
    }
    return tmp;
}

/* Function to handle File and Stdio Inputs
 * Inputs:
 *      infile: Pointer to a file or stdio
 * Returns:
 *      A **char to a formatted list of strings
 */
char **format_file(FILE *infile) 
{
    /*Check the input is good*/
    if(infile == NULL){
        fprintf(stderr,"Invalid File Input\n");
        exit(1);
    }

    /*Initialize some vars*/
    size_t nchar = 0;
    int num_lines = 0;
    char **lines = NULL;
    char *tmp = NULL;

    /*Get lines from the file, Copy them into a **char */
    while((getline(&tmp,&nchar,infile) != -1)){
        lines = (char**) srealloc (lines , sizeof(char*)*(num_lines + 1) );
        lines[num_lines] = tmp;
        tmp = NULL;
        num_lines++;
    }

    /*Send list off to be formatted*/
    return format_lines(lines,num_lines);
}

/*Checks if a *char is a proper formatting parameter
 *  Inputs:
 *      *cp: *char to be checked
 *      *num: point to an int
 *  Outputs:
 *      *num is modified via the command if it is valid
 *  Notes:
 *      This function doesn't accound for bad *num returns
 *      the calling function should deal with that logic.
 */
void param_scan(char *cp, int *num)
{
    /*Initialize those Vars*/
    char ctmp = ' ';
    int dtmp = 0;

    /*Checks if the formatting is correct*/
    if(sscanf(cp,"%[+-]%d",&ctmp,&dtmp)!= 2){
        if(sscanf(cp,"%d",&dtmp) != 1){
            fprintf(stderr,"Format Command, %s, is invalid\n",cp);
            exit(1);
        }
    }

    /*Adds, Subtracts, or changes *num */
    if(ctmp == '+'){
        (*num) = (*num) + dtmp;
    }else if(ctmp == '-'){
        (*num) = (*num) - dtmp;
    }else{
        (*num) = dtmp;
    }
}

/*Checks if a Line is a formatting command and implements change logic
 *  Inputs:
 *      *line: Line to be checked
 *  Globals:
 *      Struct settings: holds the formatting settings
 *  Returns:
 *      1 if settings is unchanged
 *      0 if settings is changed
 *  Effects:
 *      Settings stuct wil be changed if the line contains a command
 */
int check_formatting(char *line)
{
    /*Initialize those Vars*/
    int tmp = 1;
    if(strlen(line) == 1){return 1;}
    char *tp = (char*)smalloc((strlen(line)+1) * sizeof(char));
    char *start = NULL;

    /*Copy the memory so strtok doesn't kill our Line*/
    strncpy(tp,line,strlen(line)+1);
    start = tp; /*start is used to keep track of the start of the malloc'd memory*/
    
    /*Check if the first word of the line is a command*/
    /*Then call param_scan if needed*/
    /*finally check if the value is valid, then change to valid value*/
    tp = strtok(tp," \n\t");
    if(!strncmp(tp,"?mode",5)){
        tp = strtok(NULL," \n\t");
        if (tp != NULL && !strncmp(tp,"filled",6)){
            settings.mode = 1;
        }else{
            settings.mode = 0;
        }
        tmp = 0;
    }
    if(!strncmp(tp,"?pagewidth",10)){
        if(settings.mode == 0){settings.mode = 1;}
        tp = strtok(NULL," \n\t");
        if(tp != NULL){
            param_scan(tp,&settings.pagewidth);
        }
        if(settings.pagewidth < 0 ){
            settings.pagewidth = 0;
        }
        tmp = 0;
    }
    if(!strncmp(tp,"?margin",7)){
        tp = strtok(NULL," \n\t");
        if(tp != NULL){
            param_scan(tp,&settings.margin);
        }
        if (settings.margin > settings.pagewidth - 20){
            settings.margin = settings.pagewidth - 20;
        }else if(settings.margin < 0){
            settings.margin = 0;
        }
        tmp = 0;
    }
    /*Free Up that Memory*/
    if (start != NULL){
        free(start);
        start = NULL;
        tp = NULL;
    }
    return tmp;
}
/*Adds the margin to our line buffer
 *  Globals:
 *      buffer: The line buffer
 *  Effects:
 *      Adds margin to the line buffer
 */
void marg(){
    char *tmp = (char*) smalloc(sizeof(char)*(settings.margin+1));
    int i;
    for(i = 0 ; i < settings.margin ; i++){
        tmp[i] = ' ';
    }
    tmp[i] = '\0';
    strcat(buffer,tmp);
    free(tmp);
}

/*Adds current line buffer to the print buffer **char, then calls marg
 *  Globals:
 *      buffer: The line buffer
 *      format_buffer: The buffer to be returned to the printing function
 *  Effects:
 *      Adds buffer to format_buffer
 *      Resets buffer, and adds a margin to it
 */
void add_to_buffer(){
    /*Add buffer to format_buffer*/
    format_buffer = (char**) srealloc (format_buffer,sizeof(char*)*(buffer_lines+1));
    format_buffer[buffer_lines] = buffer;
    buffer_lines++;
    /*Allocate new space for buffer*/
    buffer = (char*) smalloc (sizeof(char)*(settings.pagewidth+1));
    /*Null Terminator and margin added to buffer*/
    buffer[0] = '\0';
    marg();
}

/*Holds logic for formatting the lines
 *  Inputs:
 *      *line: a line to be added to the buffers
 *  Globals:
 *      buffer: The line buffer
 *      format_buffer: The buffer to be returned to the printing function
 *      Struct settings: to check the formatting settings
 * Effects:
 *      Modifies the buffers
 */
void make_line(char *line)
{
    /*Make a bad input isn't passed in*/
    if(line == NULL){
        fprintf(stderr,"Error, Line == NULL\n");
        exit(1);
    }
    
    /*Initialize those variables*/
    char *cp = (char*) smalloc (sizeof(char) * (strlen(line) +1));
    char *start = NULL;
    strncpy(cp,line,(strlen(line)+1));
    start = cp;
     
    /*checks if the line is a formatting command*/
    if(!check_formatting(cp)){
        /*returns to avoid adding line to return buffer*/
        return;
    }

    /*Checks if formating mode is on*/
    if(settings.mode){
        /*Make sure we have some buffer to put some char in*/
        if(buffer == NULL){
            buffer = (char*) smalloc (sizeof(char)*(settings.pagewidth+1));
            buffer[0] = '\0'; /*Null terminator*/
         }

        if(!strcmp(cp,"\n")){
            /*adds empty line to return buffer*/
            /*Also checks if the last line was \n to avoid printing extra lines*/
            if(strlen(buffer) != 0){
                add_to_buffer();
                buffer[0] = '\0';
                add_to_buffer();
            }else{
                buffer[0] = '\0';
                add_to_buffer();
            }
            /*Free up that Memory*/
            free(start);   /*Note: start = start of malloc'd memory*/
            start = NULL;
            cp = NULL;
            buffer = NULL; /*This lets the same logic for empty buffer run to avoid an extra \n */

            /*returns from fuction to avoid any nasty side effect of strtok */
            return;
        } else {
            /*Otherwise we tokenize this cp*/
            cp = strtok(cp," \n\t");
        }

        /*Checks if the buffer is empty*/
        if(strlen(buffer) == 0){
            marg();
        /*Does it over flow? If not we need a space*/
        }else if( !(strlen(buffer) + strlen(cp) + sizeof(char) > settings.pagewidth) ){
            strcat(buffer," ");
        /*If it does overflow we add to the return buffer*/
        }else{
            add_to_buffer();
        }
        /*This tokenizes the current cp and adds the words to the buffer*/
        while(cp != NULL){
            /*Add Current Token to the buffer*/
            strcat(buffer,cp);

            /*Tokenize Cp*/
            cp = strtok(NULL," \n\t");
            
            /*Make sure something doesn't try to use a NULL cp*/
            if (cp == NULL){
                break;
            }

            /*Add a Space, or at current line to return buffer*/
            if(!(strlen(buffer) + strlen(cp) + sizeof(char) > settings.pagewidth)){
                strcat(buffer," ");
            }else{
                add_to_buffer();
            }   
        }
    }else{
        /*Adds Unformatted line to the return Buffer*/
        format_buffer = (char**) srealloc (format_buffer,sizeof(char*)*(buffer_lines +1));
        format_buffer[buffer_lines] = line;
        format_buffer[buffer_lines][strlen(line)-1] = '\0';
        buffer_lines++;
	}
    /*Free up that Memory*/
    free(start); /*Note: start = start of malloc'd memory*/
    start = NULL;
    cp = NULL;
}

/* Sends lines for a **char to be formatted
 *  Inputs:
 *      **lines: array of strings to be formatted
 *      num_lines: Number of lines in the array
 *  Globals:
 *      buffer: The line buffer
 *      format_buffer: The buffer to be returned to the printing function
 *  Returns:
 *      An array of formatted strings to be printed
 *  Notes:
 *      Does effect the format_buffer
 */
char **format_lines(char **lines, int num_lines) 
{
    int i;
    /*send the lines to the buffer building function*/
    for (i = 0; i < num_lines; i++){
        make_line(lines[i]);
    }
    /*adds what ever is leftover in the buffer to return buffer*/
    add_to_buffer();

    /*Adds the Null terminating line*/
    format_buffer = (char**) srealloc (format_buffer,sizeof(char*)*(buffer_lines));
    format_buffer[buffer_lines] = NULL;
    return format_buffer;
}

